#!/usr/bin/env python

import hashlib
import os
from os.path import expanduser
import PyQt4
PyQt4 = PyQt4  # to keep linter happy
from PyQt4 import QtCore, QtGui, QtSvg
from PyQt4.QtCore import QObject
try:
    from PyQt4.QtCore import QString
except ImportError:
    # we are using Python3 so QString is not defined
    QString = type("")
import signal


HOME = expanduser("~")
DOWNLOADS_DIR = os.path.join(HOME, 'Downloads')
WATCH_DIRS = [DOWNLOADS_DIR]


# If you'd like me to add a file, I need a way to get a secure hash of it. One
# way is if the file is available over https. Another is if there are published
# hashes available over https. Another is if it's gpg signed. If I can get the
# hash (pretty) securely, send me the info to FIRSTNAME@benamy.info. My first
# name is "daniel".
#
# In the future, the server component will make it much easier for the system
# to learn hashes of new files.
SHA1_DB = {
    u'empty.txt':                               'da39a3ee5e6b4b0d3255bfef95601890afd80709',  # for debugging
    u'long file name for testing.txt':          'da39a3ee5e6b4b0d3255bfef95601890afd80709',  # for debugging
    u'LibreOffice_4.0.1_MacOS_x86.dmg':         'c76ecfeb09647a345d66a79de90f606e24396414',  # daniel@benamy.info downloaded over https and shasum-ed
}


class NewFileWatcher(QObject):
    newFile = QtCore.pyqtSignal(QString, QString)  # args are: dir, file name

    def __init__(self):
        QObject.__init__(self)

        self._dirLists = {}
        for dirPath in WATCH_DIRS:
            self._dirLists[dirPath] = set(os.listdir(dirPath))

        self._fsWatcher = QtCore.QFileSystemWatcher()
        self._fsWatcher.addPaths(WATCH_DIRS)
        self._fsWatcher.directoryChanged.connect(self.fileSystemChanged)

    def fileSystemChanged(self, path):
        print "Change in %s" % path
        for dirPath in self._dirLists:
            newList = set(os.listdir(dirPath))
            print "List of dir '%s': %s" % (dirPath, newList)
            added = newList - self._dirLists[dirPath]
            print "Added: %s" % added
            self._dirLists[dirPath] = newList
            for path in added:
                print "Added to %s: %s" % (dirPath, path)
                self.newFile.emit(QString(dirPath), QString(path))


class FileVerifier(QObject):
    notification = QtCore.pyqtSignal(QString, QString, int, int)  # args are: title, msg, icon, time in ms

    def checkFileLogError(self, fileDir, fileName):
        try:
            self.checkFile(fileDir, fileName)
        except Exception as error:
            print "Exception checking file: %s" % error
            self.notification.emit(
                "Error", "There was an error checking a file.",
                QtGui.QSystemTrayIcon.Warning, 3000)

    def checkFile(self, fileDir, fileName):
        fileDir = unicode(fileDir.toUtf8())
        fileName = unicode(fileName.toUtf8())

        if fileName.startswith('.com.google.Chrome.') or fileName.endswith('crdownload'):
            print "Ignoring chrome temp download file '%s'" % fileName
            return

        if fileName in SHA1_DB:
            path = os.path.join(fileDir, fileName)
            if self.sha1File(path) == SHA1_DB[fileName]:
                self.notification.emit(
                    "'%s' looks good!" % ellipsize(fileName), "",
                    QtGui.QSystemTrayIcon.Information, 3000)
            else:
                self.notification.emit(
                    "WARNING: something is fishy",
                    "'%s' may be incomplete or corrupt, or someone may be tampering "
                    "with your internet traffic." % ellipsize(fileName),
                    QtGui.QSystemTrayIcon.Critical, 5000)
        else:
            self.notification.emit(
                "Can't help with this one",
                "Sorry, '%s' isn't in the database and can't be checked yet." %
                ellipsize(fileName),
                QtGui.QSystemTrayIcon.Information, 3000)

    def sha1File(self, path):
        sha1 = hashlib.sha1()
        f = open(path, 'rb')
        while True:
            buf = f.read(16 * 1024 * 1024)  # read 16MB at a time
            if not buf:
                break
            sha1.update(buf)
        f.close()
        return sha1.hexdigest()


class Gui(QObject):
    def __init__(self):
        menu = QtGui.QMenu()
        menu.addAction('Quit', self._quit)
        menu.aboutToShow.connect(self._aboutToShow)

        self.trayIcon = QtGui.QSystemTrayIcon()
        self.trayIcon.setContextMenu(menu)
        self.painter, self.pixmap, self.svg, self.icon = self._loadIconFromSvg('check.svg')
        self.trayIcon.setIcon(self.icon)
        self.trayIcon.show()

    def __del__(self):
        # Things have to be deleted in a certain order or it'll segfault.
        del self.painter, self.pixmap, self.svg  # avoid the paint device getting
        del self.trayIcon, self.icon             # deleted before the painter

    def _quit(self):
        QtGui.QApplication.quit()

    def _aboutToShow(self):
        print 'about to show'

    def _loadIconFromSvg(self, svgPath):
        svg = QtSvg.QSvgRenderer(svgPath)
        if not svg.isValid():
            raise RuntimeError('bad SVG')
        pixmap = QtGui.QPixmap(16, 16)
        painter = QtGui.QPainter(pixmap)
        svg.render(painter)
        # Supposedly can give QIcon a png and skip all the svg stuff but it froze
        # when I tried.
        icon = QtGui.QIcon(pixmap)
        return painter, pixmap, svg, icon


def ellipsize(s, max_len=50):
    if len(s) > max_len:
        return s[:12] + '...'
    else:
        return s


def main():
    app = QtGui.QApplication([])
    gui = Gui()
    watcher = NewFileWatcher()
    verifier = FileVerifier()

    watcher.newFile.connect(verifier.checkFileLogError)
    verifier.notification.connect(gui.trayIcon.showMessage)

    # Allow program to be interrupted with Ctrl+C
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    nice_dirs = ellipsize(', '.join(WATCH_DIRS))
    gui.trayIcon.showMessage(
        "Valium's got your back",
        "Valium is watching '%s' for new downloads." % nice_dirs,
        QtGui.QSystemTrayIcon.Information,
        5000)

    app.exec_()


if __name__ == '__main__':
    main()
